this in JavaScript
December 12, 2019
上次谈到执行环境的时候,也谈到了 this,说道 this 值实则与函数无关,而与上下文有关。再者,this 的值在创建上下文的时候就已经确定了,无法在 runtime 中改变,例如
function A() {
let a = b
this = a
} // 这样当然不可以了,但是在python却可以
这篇文章将探讨创建上下文时,this 值将如何改变。
引用类型-ReferenceType
在这之前先看一下 JavaScript 內建类型的伪代码
var valueOfReferenceType = {
base: <base object>,
propertyName: <property name>
};
引用类型的值只有两种情况:
当处理一个标识符时;
或处理一个属性访问器;
// 标识符
var a
function a() {}
//属性访问器
var a = { b: 1 }
a.b //是一个引用类型,且是一个属性访问器,他的base是a
而对于关联的 this 值的改变,则只会和以下情况有关:
在一个函数上下文中,this 的值由调用者 caller 提供,且由调用函数的方式决定(即由函数如何调用决定)。
如果调用括号(…)的左边是引用类型的值,this 将设为这个引用类型值的 base 对象
在其他情况下(与引用类型不同的任何其它属性),this 的值都为 null。不过,实际不存在 this 的值为 null 的情况,因为当 this 的值为 null 的时候,其值会被隐式转换为全局对象。
可以看以下例子
var foo = {
bar: function () {
alert(this)
},
}
var bar = foo.bar
foo.bar() // Reference, OK => foo
bar() // Reference global
foo.bar() // Reference, OK => foo
;(foo.bar = foo.bar)() // global
;(false || foo.bar)() // global
;(foo.bar, foo.bar)() // global
;(function () {
alert(this)
})() // global
第一种调用适用第一种情况,this 是由引用类型 foo.bar 调用,base 是 foo,所以 this 指向 foo。
第二种情况,this 由引用类型 foo.bar 调用,base 是 bar,this 指向 global
第三种情况则适用第二种情况,由左侧括号调用,而左值则是引用类型,base 为 foo。
第四种情况, 赋值运算符调用 getValue 方法,返回结果是函数对象,不是引用类型,则 this 设定为 global
第五种、第六种情况也一样,使用操作符调用了 getValue 方法,得到函数对象,同样设定为 global。
第七种情况可以看到左值为函数对象时是怎么作用的,同样设定为 global。
来看一件更酷的东西
function foo() {
alert(this)
}
foo() // global
alert(foo === foo.prototype.constructor) // true
// another form of the call expression
foo.prototype.constructor() // foo.prototype
这个例子充分印证 this 值在由不同引用句柄调用而发生的变化。
当直接调用的时候,引用类型的 base 为 global,自然 this 指向 global。
而当使用 foo.prototype.constructor 调用时,情况却发生了变化,由于句柄变成了 foo.prototype,所以自然 this 指向了 foo.prototype,也就是 foo 的原型对象。
base 值的改变
之前的一些情况都是在父上下文是 global 上的一些情况,并没有探讨父上下文更复杂的情况。在有些时候,base 会被设置成激活对象(Activation Object)。
看以下这个例子。
function foo() {
function bar() {
console.log(this) // global
}
bar() // the same as AO.bar()
}
这里引用类型 bar 调用了,内部函数 bar 被父函数 foo 调用,上下文的 base 则被设置成父上下文的 AO,AO 总是返回 this 的值为 null,而调用 bar(),相当于调用 AO.bar()则 null.bar(),this 指向 global。
看另一个例子
var x = 10
with ({
foo: function () {
console.log(this.x)
},
x: 20,
}) {
foo() // 20
}
使用 with 语句后,with 对象添加到 Scope chain 的前面,AO 前面插入 with 对象,此时 base 被指向到 with 对象了,foo 的 base 是 with 对象,this 指向 with 对象,this.x = 20。